<?php
	
	namespace Aspic\Security;
	
	use Aspic as A;
	
	/**
	* Make possible to create role and allow or deny them the access to action paths
	 * 
	* @Author Mallory Dessaintes <mdessaintes@gmail.com>
	* @version 1.0
	*/
	class ActionsAccessControl {
		
		use A\OptionTrait {
			__construct as optionTraitInit;
		}
		
		const ALLOW = 1;
		const DENY = 0;
		
		protected $currentRole;


		protected $_defaultActionsAccess;
		protected $_permissions;
		protected $_ressources;

		/**
		 *
		 * @param type $defaultActionsAccess Default access for action not registered with addAction()
		 */
		public function __construct($defaultActionsAccess = self::ALLOW) {
			$this->optionTraitInit();
			
			$this->opt('defaultActionsAccess', $defaultActionsAccess);
		}
		
		/**
		 * Register an action to control its access
		 * Last added ressouces have priority when they have the same priority value
		 * 
		 * @param mixed|string $actionPath The action path registered in route object (module/controller/action), 
		 * could be an array with multiple actionPaths. Jokers "*" and "?" could be used
		 * @param type $defaultAccess Default access for roles not registered with this action
		 * @param type $whenConflict When multiple rules match (getRessourceNameFromPath) with at least one that allow access and one that deny, 
		 * priority will be used to determine which to choose
		 */
		public function addRessource($ressourceName, $actionPath, $defaultAccess = self::ALLOW, $priority = 1) {
			if (!is_array($actionPath)) {
				$actionsPaths = array($actionPath);
			}
			else {
				$actionsPaths = $actionPath;
			}
			
			$this->_ressources[$ressourceName] = array(
				'actionsPaths' => $actionsPaths,
				'defaultAccess' => $defaultAccess,
				'priority' => $priority,
			);
			
			$this->_permissions = array();
			$this->currentRole = null;
		}
		
		public function grant($role, $ressourceName, $access = self::ALLOW) {
			$this->_permissions[$role]['perms'][$ressourceName] = $access;
		}
		
		/**
		 *
		 * @param type $role
		 * @param string $parentRole The name of the parent role
		 */
		public function setRoleParent($role, $parentRole) {
			$this->_permissions[$role]['parent'] = $parentRole;
		}
		
		public function isGranted($ressourceName, $role = null) {
			if ($role == null OR (is_array($role) AND !count($role))) {
				if ($this->currentRole === null OR (is_array($this->currentRole) AND !count($this->currentRole))) {
//					throw new Exception('Current role is not set');
					$role = null;
				}
				else {
					$role = $this->currentRole;
				}
			}
			
			if (is_array($role)) {
				$res = false;
				
				foreach ($role as $r) {
					$res = ($res OR $this->isGranted($ressourceName, $r));
				}
				
				return $res;
			}
			else {
				// Resource does not exists => Take default access
				if (!isset($this->_ressources[$ressourceName])) {
//					throw new Exception('Resource "'.$ressourceName.'" does not exists');
					return (bool)$this->opt('defaultActionsAccess');
				}

				$ressData = $this->_ressources[$ressourceName];
//				var_dump($ressData);
				if (is_null($role)) {
					return (bool)$ressData['defaultAccess'];
				}
				
				// Role permissions
				$roleParent = A\Util::getIfSet($this->_permissions[$role]['parent']);
				$ressAccess = A\Util::getIfSet($this->_permissions[$role]['perms'][$ressourceName]);

				if (!is_null($roleParent) AND count($roleParent['perms'])) {
					if (is_null($ressAccess)) {
						$ressAccess = $this->_permissions[$roleParent]['perms'];
					}
					else {
						$ressAccess = array_merge($this->_permissions[$roleParent]['perms'], $ressAccess);
					}
				}
//				var_dump((bool)$ressData['defaultAccess'], $role);
				// Rule is set so we return access
				if(!is_null($ressAccess)) {
					return (bool)$ressAccess;
				}
				// Rule not set for this role, return ressource default access
				else {
					return (bool)$ressData['defaultAccess'];
				}
			}
		}
		
		/**
		 * 
		 * @param type $path
		 * @return type 
		 */
		public function getRessourceNameFromPath($path) {
			$match = null;
			
			foreach ($this->_ressources as $ressName => $ressourceData) {
				$ressourcePaths = $ressourceData['actionsPaths'];
				
				foreach ($ressourcePaths as $ressPath) {
					$regex = '#\\A'.str_replace(array('*', '?'), array('.+', '.'), $ressPath).'\\z#';
//					var_dump($regex, $path);
					if (preg_match($regex, $path)) {
//						$oldMatchStars = str_word_count($string);
						if (is_null($match) OR $this->_ressources[$ressName]['priority'] >= $this->_ressources[$match]['priority']) {
							$match = $ressName;
						}
					}
				}
			}
			
			return $match;
		}
		
		public function isGrantedFromPath($path) {
			$ressourceName = $this->getRessourceNameFromPath($path);
//			var_dump($this->isGranted($ressourceName), $path);
			return $this->isGranted($ressourceName);
		}
		
		public function setCurrentRole($role) {
			$this->currentRole = $role;
		}
		
		public function getCurrentRole() {
			return $this->currentRole;
		}
		
	}
		
	
?>